package gmu.swe.project.peer;

import gmu.swe.project.io.ConfigFileParser;
import gmu.swe.project.io.DirectoryReader;
import gmu.swe.project.query.Query;
import gmu.swe.project.query.QueryResult;
import gmu.swe.project.query.QueryResultSet;
import gmu.swe.project.registry.RegistryInfo;
import gmu.swe.project.registry.TrackerRegistration;
import gmu.swe.project.tracker.TrackerInfo;
import gmu.swe.project.utils.Helper;

import java.awt.event.*;
import javax.swing.*;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Observable;
import java.util.Observer;
import java.util.Random;
import java.util.UUID;
import java.awt.Dimension;
import javax.swing.JPanel;
import javax.swing.JTextField;
import java.awt.Point;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.JProgressBar;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableModel;

import java.awt.BorderLayout;

public class PeerGUI implements Observer {

	private static Socket peerSocket = null; // @jve:decl-index=0:
	private static ObjectOutputStream oos = null;
	private static ObjectInputStream ois = null; // @jve:decl-index=0:

	private static ArrayList<RegistryInfo> registryServers = null;
	
	private static Integer interval = 15;

	private JFrame jFrame = null; // @jve:decl-index=0:visual-constraint="10,20"
	private JPanel jContentPane = null;
	private JButton btnPause = null;
	private JButton btnResume = null;
	private JButton btnCancel = null;
	private JButton btnClear = null;
	private JPanel PanelButtons = null;
	private JPanel addPanel = null;
	private JButton btnDownload = null;
	private JPanel DownloadPanel = null;
	private JTable table = null;
	private JPanel SearchResultsPanel = null;
	private JPanel SearchInputPanel = null;
	private JLabel lblSearch = null;
	private JTextField txtSearch = null;
	private JButton btnSearch = null;
	static String sharedDirectory = "";
	static int uploadPort;
	static PeerInfo info = new PeerInfo();
	static TrackerInfo leastWorkloadTrackerInfo = null;

	private DefaultTableModel resultModel = null;
	private String[] cols = { "Peer", "Port", "File Name", "Workload" };
	
	private static String trackerInfoIP;
	static Integer trackerInfoPort;

	// Flag for whether or not table selection is being cleared.
	private boolean clearing;

	// Download table's data model.
	private DownloadsTable tableModel = null;
	// Currently selected download.
	private DownloaderThread selectedDownload;  //  @jve:decl-index=0:
	private static UploaderService uploader;
	private JTable ResultTable = null;

	private JLabel uploads = null;  //  @jve:decl-index=0:visual-constraint="673,147"
	private JLabel lblUpload = null;  //  @jve:decl-index=0:visual-constraint="683,192"
	
	private JFrame getJFrame() {
		if (jFrame == null) {
			jFrame = new JFrame();
			jFrame.setTitle("Hybrid P2P File Sharing System");
			jFrame.setSize(new Dimension(640, 569));
			jFrame.setContentPane(getJContentPane());
			jFrame.addWindowListener(new WindowAdapter() {
				public void windowClosing(WindowEvent e) {
					System.exit(0);
				}
			});
		}
		return jFrame;
	}

	private JPanel getJContentPane() {
		if (jContentPane == null) {
			jContentPane = new JPanel();
			jContentPane.setLayout(null);
			jContentPane.add(getSearchInputPanel(), null);
			jContentPane.add(getSearchResultsPanel(), null);
			jContentPane.add(getPanelButtons(), null);
			jContentPane.add(getAddPanel(), null);
			jContentPane.add(getDownloadPanel(), null);
		}
		return jContentPane;
	}

	private JPanel getSearchResultsPanel() {
		if (SearchResultsPanel == null) {
			SearchResultsPanel = new JPanel();
			SearchResultsPanel.setLayout(new BorderLayout());
			SearchResultsPanel.setBorder(BorderFactory
					.createTitledBorder("Results"));
			SearchResultsPanel.setSize(new Dimension(633, 200));
			SearchResultsPanel.setLocation(new Point(0, 30));
			SearchResultsPanel.add(new JScrollPane(getResultTable()),
					BorderLayout.CENTER);
		}
		return SearchResultsPanel;
	}

	private JPanel getSearchInputPanel() {
		if (SearchInputPanel == null) {
			SearchInputPanel = new JPanel();
			SearchInputPanel.setLayout(null);
			SearchInputPanel.setSize(new Dimension(632, 30));
			SearchInputPanel.add(getTxtSearch());
			SearchInputPanel.add(getLblSearch());
			SearchInputPanel.add(getBtnSearch());
			SearchInputPanel.add(getUploads());
			SearchInputPanel.add(getLblUpload());
		}
		return SearchInputPanel;
	}

	private JPanel getAddPanel() {
		if (addPanel == null) {
			addPanel = new JPanel();
			addPanel.setLayout(null);
			addPanel.setSize(new Dimension(631, 32));
			addPanel.setLocation(new Point(-1, 228));
			addPanel.add(getBtnDownload(), null);
		}
		return addPanel;
	}

	private JPanel getPanelButtons() {
		if (PanelButtons == null) {
			PanelButtons = new JPanel();
			PanelButtons.setLayout(null);
			PanelButtons.setSize(new Dimension(625, 48));
			PanelButtons.setLocation(new Point(2, 485));
			PanelButtons.add(getBtnPause(), null);
			PanelButtons.add(getBtnCancel(), null);
			PanelButtons.add(getBtnResume(), null);
			PanelButtons.add(getBtnClear(), null);
		}
		return PanelButtons;
	}

	private JPanel getDownloadPanel() {
		if (DownloadPanel == null) {
			DownloadPanel = new JPanel();
			DownloadPanel.setLayout(new BorderLayout());
			DownloadPanel.setBorder(BorderFactory
					.createTitledBorder("Downloads"));
			DownloadPanel.setSize(new Dimension(632, 226));
			DownloadPanel.setLocation(new Point(1, 258));
			DownloadPanel.add(new JScrollPane(getTable()), BorderLayout.CENTER);
		}
		return DownloadPanel;
	}

	private DownloadsTable getTableModel() {
		if (tableModel == null) {
			tableModel = new DownloadsTable();
		}
		return tableModel;
	}

	private JTable getTable() {
		if (table == null) {
			table = new JTable(getTableModel());
			table.setSize(new Dimension(122, 64));
			table.getSelectionModel().addListSelectionListener(
					new ListSelectionListener() {
						public void valueChanged(ListSelectionEvent e) {
							tableSelectionChanged();
						}
					});
			// Allow only one row at a time to be selected.
			table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

			// Set up ProgressBar as renderer for progress column.
			ProgressRenderer renderer = new ProgressRenderer(0, 100);
			renderer.setStringPainted(true); // show progress text
			table.setDefaultRenderer(JProgressBar.class, renderer);

			// Set table's row height large enough to fit JProgressBar.
			table.setRowHeight((int) renderer.getPreferredSize().getHeight());
		}
		return table;
	}

	private JTable getResultTable() {
		if (ResultTable == null) {
			resultModel = new DefaultTableModel();
			for (String s : cols) {
				resultModel.addColumn(s);
			}
			ResultTable = new JTable(resultModel);
			ResultTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
			ResultTable.setSize(new Dimension(379, 92));
		}
		return ResultTable;
	}

	// Create Buttons
	private JButton getBtnDownload() {
		if (btnDownload == null) {
			btnDownload = new JButton();
			btnDownload.setSize(new Dimension(114, 22));
			btnDownload.setLocation(new Point(259, 5));
			btnDownload.setText("Download");
			btnDownload.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					actionAdd();
				}
			});
		}
		return btnDownload;
	}

	private JButton getBtnPause() {
		if (btnPause == null) {
			btnPause = new JButton("Pause");
			btnPause.setSize(new Dimension(85, 19));
			btnPause.setLocation(new Point(130, 16));
			btnPause.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					actionPause();
				}
			});
		}
		return btnPause;
	}

	private JButton getBtnCancel() {
		if (btnCancel == null) {
			btnCancel = new JButton("Cancel");
			btnCancel.setSize(new Dimension(85, 19));
			btnCancel.setLocation(new Point(309, 16));
			btnCancel.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					actionCancel();
				}
			});
		}
		return btnCancel;
	}

	private JButton getBtnResume() {
		if (btnResume == null) {
			btnResume = new JButton("Resume");
			btnResume.setSize(new Dimension(85, 19));
			btnResume.setLocation(new Point(219, 16));
			btnResume.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					actionResume();
				}
			});
		}
		return btnResume;
	}

	private JButton getBtnClear() {
		if (btnClear == null) {
			btnClear = new JButton("Clear");
			btnClear.setSize(new Dimension(85, 19));
			btnClear.setLocation(new Point(398, 16));
			btnClear.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					actionClear();
				}
			});
		}
		return btnClear;
	}

	private JButton getBtnSearch() {
		if (btnSearch == null) {
			btnSearch = new JButton("Go");
			btnSearch.setSize(new Dimension(85, 20));
			btnSearch.setLocation(new Point(535, 5));
			btnSearch.addActionListener(new java.awt.event.ActionListener() {
				public void actionPerformed(java.awt.event.ActionEvent e) {
					searchFile();
				}
			});
		}
		return btnSearch;
	}

	private JTextField getTxtSearch() {
		if (txtSearch == null) {
			txtSearch = new JTextField();
			txtSearch.setSize(new Dimension(191, 20));
			txtSearch.setLocation(new Point(342, 6));
		}
		return txtSearch;
	}

	private JLabel getLblSearch() {
		if (lblSearch == null) {
			lblSearch = new JLabel();
			lblSearch.setText("Search:");
			lblSearch.setLocation(new Point(293, 3));
			lblSearch.setSize(new Dimension(58, 25));
		}
		return lblSearch;
	}

	private JLabel getUploads() {
		if (uploads == null) {
			uploads = new JLabel();
			uploads.setText("0");
			uploads.setLocation(new Point(116, 8));
			uploads.setSize(new Dimension(60,15));
			}
		return uploads;
	}
	private JLabel getLblUpload() {
		if (lblUpload == null) {
			lblUpload = new JLabel();
			lblUpload.setText("Current Uploads:");
			lblUpload.setLocation(new Point(8, 3));
			lblUpload.setSize(new Dimension(111, 24));
		}
		return lblUpload;
	}

	// Add a new download.
	private void actionAdd() {
		int row = ResultTable.getSelectedRow();
		if(row >= 0)
		{
			String filename = (String) ResultTable.getModel().getValueAt(row, 2);
			tableModel.addDownload(new DownloaderThread(trackerInfoIP, trackerInfoPort, filename, sharedDirectory));
		}
	}

	// Pause the selected download
	private void actionPause() {
		selectedDownload.pause();
		updateButtons();
	}

	// Resume the selected download.
	private void actionResume() {
		selectedDownload.resume();
		updateButtons();
	}

	// Cancel the selected download.
	private void actionCancel() {
		selectedDownload.cancel();
		updateButtons();
	}

	// Clear the selected download.
	private void actionClear() {
		clearing = true;
		tableModel.clearDownload(table.getSelectedRow());
		clearing = false;
		selectedDownload = null;
		updateButtons();
	}

	/*
	 * Update each button's state based off of the currently selected download's
	 * status.
	 */
	private void updateButtons() {
		if (selectedDownload != null) {
			int status = selectedDownload.getStatus();
			switch (status) {
			case 0:
				btnPause.setEnabled(true);
				btnResume.setEnabled(false);
				btnCancel.setEnabled(true);
				btnClear.setEnabled(false);
				break;
			case 1:
				btnPause.setEnabled(false);
				btnResume.setEnabled(true);
				btnCancel.setEnabled(true);
				btnClear.setEnabled(false);
				break;
			case 4:
				btnPause.setEnabled(false);
				btnResume.setEnabled(true);
				btnCancel.setEnabled(false);
				btnClear.setEnabled(true);
				break;
			default: // COMPLETE or CANCELLED
				btnPause.setEnabled(false);
				btnResume.setEnabled(false);
				btnCancel.setEnabled(false);
				btnClear.setEnabled(true);
			}
		} else {
			// No download is selected in table.
			btnPause.setEnabled(false);
			btnResume.setEnabled(false);
			btnCancel.setEnabled(false);
			btnClear.setEnabled(false);
		}
	}

	// Called when table row selection changes.
	private void tableSelectionChanged() {
		/*
		 * Unregister from receiving notifications from the last selected
		 * download.
		 */
		if (selectedDownload != null)
			selectedDownload.deleteObserver(PeerGUI.this);

		/*
		 * If not in the middle of clearing a download, set the selected
		 * download and register to receive notifications from it.
		 */
		if (!clearing) {
			selectedDownload = tableModel.getDownload(table.getSelectedRow());
			selectedDownload.addObserver(PeerGUI.this);
			updateButtons();
		}
	}

	private void searchFile() {
		try {
			// send the server a query
			resultModel.setRowCount(0);

			String queryFile = txtSearch.getText();
			
			System.out.println("Searching for file: " + queryFile);
			// Add the query to a message. ID will be returned in the ACK
			Query query = new Query();
			query.setQuery(queryFile);
			query.setId(UUID.randomUUID().toString());
			query.setType(Query.PEER_QUERY);

			// send the Query to the server
			
			peerSocket = new Socket(trackerInfoIP, trackerInfoPort);
			System.out.println(peerSocket+" and tracker port "+trackerInfoPort);
			oos = new ObjectOutputStream(peerSocket.getOutputStream());
			ois = new ObjectInputStream(peerSocket.getInputStream());
			
			System.out.println("Sending Query to Server.");
			oos.writeObject(query);
			
			// wait for the response
			System.out.println("Waiting for response.");
			QueryResultSet queryResultSet = (QueryResultSet) ois.readObject();
			ArrayList<QueryResult> ar = queryResultSet.getQueryResultSet();
			
			System.out.println("Found: "+ Integer.toString(ar.size())+" Files");
			for (int i = 0; i < ar.size(); i++){
				QueryResult qr = ar.get(i);
				String[] row = new String[4];
				row[0] = qr.getPeer();
				row[1] = Integer.toString(qr.getPort());
				row[2] = qr.getFileName();
				row[3] = qr.getWorkload().toString();
				resultModel.addRow(row);
			}
			peerSocket.close();
			oos.close();
			ois.close();
		} catch (IOException ioex) {
			System.out.println(ioex.toString());
		} catch (ClassNotFoundException cnfex) {
			System.out.println(cnfex.toString());
		}
	}

	public void update(Observable o, Object arg) {
		// Update buttons if the selected download has changed.
		if (selectedDownload != null && selectedDownload.equals(o))
			updateButtons();
		
		info.setWorkload(uploader.UploadsCount());
		uploads.setText(Integer.toString(uploader.UploadsCount()));
	}

	private static PeerInfo readConfigFile() {

		// find config file

//		String location = System.getProperty("CONFIG_FILE_LOCATION", "C:\\Temp\\config.xml");
//		System.out.println("location: " + location);
//		ConfigFileParser parser = new ConfigFileParser(location);

		ConfigFileParser parser = Helper.getParser();
		
		// Create the object that will be sent to the server
		info.setIpAddress(parser.getIpAddress());
		info.setPort(uploadPort);
		// get the list of files in the directory
		DirectoryReader reader = new DirectoryReader(parser
				.getSharedDirectory());
		if (reader.queryDirectory()) {
			info.setFiles(reader.getFilesInDirectory());
		}
		
		// set the unique id on the peer info; it will be returned in the ACK
		info.setId(UUID.randomUUID().toString());
		sharedDirectory = parser.getSharedDirectory();
		registryServers = parser.getRegistryServers();
		interval = parser.getPeerInterval();
		return info;
	}

	public static void main(String[] args) throws Exception {

		uploadPort = 3000 + new Random().nextInt(5000);
		PeerInfo peerInfo = readConfigFile();
		uploader = new UploaderService(uploadPort, sharedDirectory);
		
		PeerGUI p = new PeerGUI();
		p.getJFrame().setVisible(true);
		
		//Get tracker registration from one of the registries, traverse
		//through the tracker registration to find the tracker info object
		//that has the least amount of workload
		System.out.println("PeerGUI::main():getting registry");
		TrackerRegistration tr = getTrackerRegistration();
		if (tr == null) {
			throw new Exception("All Registries are down!");
		}
		leastWorkloadTrackerInfo = getLeastWorkloadTracker(tr);
		trackerInfoIP = leastWorkloadTrackerInfo.getIpAddress();
		trackerInfoPort = leastWorkloadTrackerInfo.getListeningPort();

		//start the keep alive message to the tracker
		new PeerKeepAlivePingThread(interval, leastWorkloadTrackerInfo).start();
		
		uploader.addObserver(p);

		try {
			// create new socket
			peerSocket = new Socket(trackerInfoIP, trackerInfoPort);
			// connect an output stream to it to send data to the server
			oos = new ObjectOutputStream(peerSocket.getOutputStream());
			// connect a reader on it to receive data from the server
			ois = new ObjectInputStream(peerSocket.getInputStream());
			// send the PeerInfo to the server
			oos.writeObject(peerInfo);
			
			// once the client has an ACK back it's info has been successfully
			// loaded onto the server
			String ack = (String) ois.readObject();

		} catch (UnknownHostException uhex) {
			// do something
		} catch (IOException ioex) {
			// do something
		} catch (ClassNotFoundException cnfex) {
			// do something
		} finally {
			// close the reader, writer and socket
			try {
				oos.close();
			} catch (Exception ex) {
			}
			try {
				ois.close();
			} catch (Exception ex) {
			}
			try {
				peerSocket.close();
			} catch (Exception ex) {
			}
		}
	}// main

	/**
	 * Traverse the trackers in the tracer registration to find the tracker that
	 * has the least workload
	 * 
	 * @param tr
	 * @return
	 */
	public static TrackerInfo getLeastWorkloadTracker(TrackerRegistration tr) {
		ArrayList<TrackerInfo> trackerSet = tr.getTrackerSet();
		Iterator<TrackerInfo> trackerSetIter = trackerSet.iterator();
		TrackerInfo tri1 = new TrackerInfo();
		if (trackerSet.size() == 0) {
			tri1 = null;
		} else if (trackerSet.size() == 1 ) {
			tri1 = trackerSetIter.next();
		} else {
			tri1 = trackerSetIter.next();
			while (trackerSetIter.hasNext()) {
				TrackerInfo tri2 = trackerSetIter.next();
				if (tri2.getWorkload() < tri1.getWorkload()) {
					tri1 = tri2;
				}
			}
		}
		return tri1;
	}// getLeastWorkloadTracker

	/**
	 * Find a live registry and get the tracker registration from it
	 */
	public static TrackerRegistration getTrackerRegistration() {
		System.out.println("PeerGUI::getTrackerRegistration():inside fxn");
		TrackerRegistration localTr = new TrackerRegistration();
		boolean connectSuccessful = false;
		try {
			Iterator<RegistryInfo> registryIter = registryServers.iterator();
			System.out.println("PeerGUI::getTrackerRegistration():num of registries found: " +
					registryServers.size());
			while (registryIter.hasNext() && !connectSuccessful) {
				RegistryInfo ri = registryIter.next();
				System.out
						.println("PeerGUI::getTrackerRegistration(): registry IP: "
								+ ri.getIpAddress()
								+ " registry port: "
								+ ri.getPort());
				Socket socketConnection = new Socket(ri.getIpAddress(), ri
						.getPort());
				// output stream to write to the server
				ObjectOutputStream clientOutputStream = new ObjectOutputStream(
						socketConnection.getOutputStream());

				// incoming stream to receive from the server
				ObjectInputStream clientInputStream = new ObjectInputStream(
						socketConnection.getInputStream());

				String messageObj = "GET_TRACKERS";
				clientOutputStream.writeObject(messageObj);

				localTr = (TrackerRegistration) clientInputStream.readObject();

				System.out
						.println("PeerGUI::getTrackerRegistration()::trackerRegistration from registry, size: "
								+ localTr.getTrackerSet().size());
				// close the streams
				clientOutputStream.close();
				clientInputStream.close();
				socketConnection.close();
				connectSuccessful = true;
			}

		} catch (Exception ex) {
			System.out
					.println("PeerGUI::getTrackerRegistration():ex: "
							+ ex);
			localTr = null;
		}
		return localTr;
	}// getTrackerRegistration
}// class PeerGUI(...
